package cn.kivensoft.util.impl;

import java.security.MessageDigest;

import cn.kivensoft.util.Crypt;
import cn.kivensoft.util.Fmt;
import cn.kivensoft.util.Strings;
import cn.kivensoft.util.impl.BaseCrypt;

public class UnixCrypt extends BaseCrypt implements Crypt {
	private static final int DEFAULT_SALT_LEN = 6;
	private static final String MAGIC = "$1$";


	public UnixCrypt() {
		super();
	}

	@Override
	public String encrypt(String passwd) {
		String salt = encodeBase64(gensalt(DEFAULT_SALT_LEN));
		try {
			return doencrypt(passwd, salt);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	public boolean verify(String passwd, String encrypt) {
		if (encrypt == null || encrypt.length() < 34
				|| encrypt.charAt(0) != MAGIC.charAt(0)
				|| encrypt.charAt(1) != MAGIC.charAt(1)
				|| encrypt.charAt(2) != MAGIC.charAt(2)
				|| encrypt.charAt(11) != MAGIC.charAt(0))
			return false;

		try {
			String enc = doencrypt(passwd, encrypt.substring(3, 11));
			return encrypt.equals(enc);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private String doencrypt(String passwd, String salt) throws Exception {
		byte[] pwd_bs = Strings.toBytes(passwd);
		byte[] salt_bs = Strings.toBytes(salt);
		byte[] magic_bs = Strings.toBytes(MAGIC);

		MessageDigest ctx = MessageDigest.getInstance("md5");
		ctx.update(pwd_bs);
		ctx.update(magic_bs);
		ctx.update(salt_bs);

		MessageDigest ctx1 = MessageDigest.getInstance("md5");
		ctx1.update(pwd_bs);
		ctx1.update(salt_bs);
		ctx1.update(pwd_bs);
		byte[] finalState = ctx1.digest();

		for (int pl = pwd_bs.length; pl > 0; pl -= 16)
			ctx.update(finalState, 0, Math.min(pl, 16));

		for (int i = finalState.length - 1; i >= 0; --i)
			finalState[i] = 0;

		for (int i = pwd_bs.length; i != 0; i >>>= 1) {
			if ((i & 1) != 0) ctx.update(finalState, 0, 1);
			else ctx.update(pwd_bs, 0, 1);
		}

		finalState = ctx.digest();

		for (int i = 0; i < 1000; i++) {
			MessageDigest ctx2 = MessageDigest.getInstance("md5");

			if ((i & 1) != 0) ctx2.update(pwd_bs);
			else ctx2.update(finalState, 0, 16);

			if ((i % 3) != 0) ctx2.update(salt_bs);
			if ((i % 7) != 0) ctx2.update(pwd_bs);
			if ((i & 1) != 0) ctx2.update(finalState, 0, 16);
			else ctx2.update(pwd_bs);

			finalState = ctx2.digest();
		}

		char[] b64 = new char[22];
		toBase64(b64,  0, 4, finalState[ 0], finalState[ 6], finalState[12]);
		toBase64(b64,  4, 4, finalState[ 1], finalState[ 7], finalState[13]);
		toBase64(b64,  8, 4, finalState[ 2], finalState[ 8], finalState[14]);
		toBase64(b64, 12, 4, finalState[ 3], finalState[ 9], finalState[15]);
		toBase64(b64, 16, 4, finalState[ 4], finalState[10], finalState[ 5]);
		toBase64(b64, 20, 2,        (byte)0,        (byte)0, finalState[11]);

		String enc = new String(b64);
		System.out.println("enc = " + enc);

		return Fmt.concat(MAGIC, salt, MAGIC.substring(0, 1), enc);
	}

	private void toBase64(char[] out, int off, int len, byte b2, byte b1, byte b0) {
		int w = ((b2 << 16) & 0x00ffffff) | ((b1 << 8) & 0x00ffff) | (b0 & 0xff);
		int n = len;
		while (n-- > 0) {
			out[off++] = BASE64_DIGEST[w & 0x3f];
			w >>= 6;
		}
	}

}
